parasails.registerPage('vulnerability-list', {
  //  ╦╔╗╔╦╔╦╗╦╔═╗╦    ╔═╗╔╦╗╔═╗╔╦╗╔═╗
  //  ║║║║║ ║ ║╠═╣║    ╚═╗ ║ ╠═╣ ║ ║╣
  //  ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
  data: {
    totalFilteredVulnerabilities: undefined,
    filteredVulnerabilities: [],
    pages: [],//« an array of page numbers representing all accessible pages given the current `filteredVulnerabilities`
    currentPageIndex: 0,
    ENTRIES_PER_PAGE: 40,//« must match view-vulnerability-list.js and get-vulnerabilities.js

    // For our filters/sorts:
    cveIdQuery: undefined,
    sortBy: 'publishedAt',//« must match view-vulnerability-list.js and get-vulnerabilities.js
    sortDirection: 'DESC',//« must match view-vulnerability-list.js and get-vulnerabilities.js
    minSeverity: 0,
    maxSeverity: 10,
    severityFilterOptions: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    teamApid: undefined,


    // For our modal
    modal: '',
    remediationSnapshots: [],
    selectedVulnerability: undefined,
    affectedHostsForSelectedVuln: [],

    // For CSV export
    formData: {},
    formErrors: {},
    formRules: {},
    exportUrl: '',

    // For loading & error states
    syncing: '',
    cloudError: '',
    syncingMessage: '',
    teamNameToDisplay: 'All teams',
    showNoVulnsFoundMessage: false,
  },

  //  ╦  ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦  ╔═╗
  //  ║  ║╠╣ ║╣ ║  ╚╦╝║  ║  ║╣
  //  ╩═╝╩╚  ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
  beforeMount: function() {
    this.totalFilteredVulnerabilities = this.totalVulnerabilities;
    this.filteredVulnerabilities = this.vulnerabilities;
    this._computePages();
  },
  mounted: async function() {
    this.currentPageIndex = this.filters.page;
    this.sortBy = this.filters.sortBy;
    this.sortDirection = this.filters.sortDirection;
    this.minSeverity = this.filters.minSeverity;
    this.maxSeverity = this.filters.maxSeverity;
    this.teamApid = this.filters.teamApid;
    this.formData = this.filters;
    await this._getVulnerabilities(this.currentPageIndex);
    this.showNoVulnsFoundMessage = true;
    this.addTableEdgeShadow();
  },

  //  ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
  //  ║║║║ ║ ║╣ ╠╦╝╠═╣║   ║ ║║ ║║║║╚═╗
  //  ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
  methods: {

    addTableEdgeShadow: function() {
      let tableContainer = document.querySelector('.table-responsive');
      if(tableContainer) {
        let isEdgeOfResultsTableVisible = tableContainer.scrollWidth - tableContainer.scrollLeft === tableContainer.clientWidth;
        if (!isEdgeOfResultsTableVisible) {
          tableContainer.classList.add('right-edge-shadow');
        }

        tableContainer.addEventListener('scroll', (event)=>{
          let container = event.target;
          let isScrolledFullyToLeft = container.scrollLeft === 0;
          let isScrolledFullyToRight = (container.scrollWidth - container.scrollLeft <= container.clientWidth + 1);
          // Update the class on the table container based on how much the table is scrolled.
          if (isScrolledFullyToLeft) {
            container.classList.remove('edge-shadow', 'left-edge-shadow');
            container.classList.add('right-edge-shadow');
          } else if (isScrolledFullyToRight) {
            container.classList.remove('edge-shadow', 'right-edge-shadow');
            container.classList.add('left-edge-shadow');
          } else if(!isScrolledFullyToRight && !isScrolledFullyToLeft) {
            container.classList.remove('left-edge-shadow', 'right-edge-shadow');
            container.classList.add('edge-shadow');
          }
        });
      }
    },

    clickPageButton: async function(pageIndex) {
      if (this.syncing) { return; }//•
      await this._getVulnerabilities(pageIndex);
      let scrollAnchor = this.$find('[purpose="page-heading"]')[0];
      window.scrollTo(document.body, scrollAnchor.offsetTop, 100);
    },

    clickSortButton: async function(sortBy) {
      let resetDirection = sortBy !== this.sortBy;
      if(resetDirection || this.sortDirection === 'ASC') {
        this.sortDirection = 'DESC';
      } else {
        this.sortDirection = 'ASC';
      }
      this.sortBy = sortBy;
      await this._getVulnerabilities(0);
    },

    clickExportOneCveCsv: async function(cveId){
      this.syncing = true;
      this.syncingMessage = 'Generating your CSV export.';
      io.socket.get('/download-one-vulnerability-csv',{cveId: cveId, teamApid: this.teamApid}, ()=>{});
      io.socket.on('singleCsvExportDone', this._handleOneCsvExport);
      return;
    },

    _handleOneCsvExport: function(data) {
      let csvExportUrl = URL.createObjectURL(new Blob([data.csv], { type: 'text/csv;' }));
      let exportDownloadLink = document.createElement('a');
      exportDownloadLink.href = csvExportUrl;
      exportDownloadLink.download = `${data.cveId}-${(new Date()).toISOString()}.csv`;
      exportDownloadLink.click();
      URL.revokeObjectURL(csvExportUrl);
      this.syncing = false;
      this.syncingMessage = '';
      // Disable the socket event listener after we start the download, We do this for
      // the download actions because if this event listener is left active, any subsequent exports will download multiple times.
      io.socket.off('singleCsvExportDone', this._handleOneCsvExport);
    },

    changeMinSeverity: async function() {
      this.currentPageIndex = 0;
      if(this.maxSeverity < this.minSeverity) {
        this.maxSeverity = 10;
      }
      await this._getVulnerabilities(0);
    },

    changeMaxSeverity: async function() {
      this.currentPageIndex = 0;
      if(this.minSeverity > this.maxSeverity) {
        this.minSeverity = 0;
      }
      await this._getVulnerabilities(0);
    },

    changeFilteredTeam: async function() {
      this.currentPageIndex = 0;
      if(this.teamApid !== 'undefined'){
        let selectedTeam = _.find(this.teamsToDisplay, {'id':this.teamApid});
        this.teamNameToDisplay = selectedTeam.name;
      } else {
        this.teamApid = undefined;
        this.teamNameToDisplay = 'All teams';
      }
      await this._getVulnerabilities(0);
    },

    clickAffectedHosts: async function(vulnerability) {
      await this._getRemediationTimeline(vulnerability.id);
      this.selectedVulnerability = vulnerability;
      this.modal = 'remediation-details';
    },

    clickChangeAffectedTeam: async function(teamName) {
      let team = _.find(this.teamsToDisplay, {'name':teamName});
      this.teamApid = team.id;
      this.teamNameToDisplay = teamName;
      await this._getVulnerabilities(0);
    },
    clickOpenCveModal: async function(vulnerability) {
      this.selectedVulnerability = vulnerability;
      this.modal = 'vulnerability-details';
    },

    clickAffectedSoftware: async function(vulnerability) {
      this.selectedVulnerability = vulnerability;
      this.modal = 'software-details';
    },

    closeModal: async function() {
      this.modal = '';
      this.remediationSnapshots = [];
      this.selectedVulnerability = undefined;
    },
    clickOpenExportModal: async function() {
      this.formData = {
        sortBy: 'cveId',
        sortDirection: this.sortDirection,
        minSeverity: this.minSeverity,
        maxSeverity: this.maxSeverity,
        teamApid: this.teamApid,
        exportType: 'resolvedAndVulnerableInstalls',
      };
      this.modal = 'export-csv';
      return;
    },

    handleSubmittingExportForm: async function() {
      this.syncing = true;
      this.syncingMessage = 'Generating your CSV export... \n\n This process may take up to 20 minutes.';
      // create a copy of the formData to prevent changing the "Team" dropdown if we need to set teamApid to undefined.
      let exportArgins = _.clone(this.formData);
      if(exportArgins.teamApid === 'undefined'){
        exportArgins.teamApid = undefined;
      }
      io.socket.get('/download-vulnerabilities-csv',exportArgins, ()=>{});
      io.socket.on('csvExportDone', this._handleExportFormResult);
      return;
    },

    _handleExportFormResult: async function(csv) {
      let csvExportUrl = URL.createObjectURL(new Blob([csv], { type: 'text/csv;' }));
      let exportDownloadLink = document.createElement('a');
      exportDownloadLink.href = csvExportUrl;
      exportDownloadLink.download = `export ${new Date().toISOString()}.csv`;
      exportDownloadLink.click();
      URL.revokeObjectURL(csvExportUrl);
      this.syncing = false;
      this.syncingMessage = '';
      // Disable the socket event listener after we start the download, We do this for
      // the download actions because if this event listener is left active, any subsequent exports will download multiple times.
      io.socket.off('csvExportDone', this._handleExportFormResult);
    },

    _getVulnerabilities: async function(pageIndex) {
      this.cloudError = '';
      this.syncing = true;
      this.syncingMessage = `Getting page ${pageIndex + 1} of vulnerabilities for ${this.teamNameToDisplay.toLowerCase()}....`;
      let report = await Cloud.getVulnerabilities.with({
        minSeverity: this.minSeverity,
        maxSeverity: this.maxSeverity,
        sortBy: this.sortBy,
        sortDirection: this.sortDirection,
        page: pageIndex,
        teamApid: this.teamApid
      })
      .tolerate((err)=>{
        this.cloudError = err;
        this.syncing = false;
      });
      if(!this.cloudError) {
        this.totalFilteredVulnerabilities = report.total;
        this.filteredVulnerabilities = [];
        // Clear out existing  `filteredVulnerabilities` and force-render before updating
        // the result set in case Vue tries to get clever.
        await this.forceRender();
        this.filteredVulnerabilities = report.entries;
        this.currentPageIndex = pageIndex;
        this._computePages();
        this.syncing = false;
      }
    },

    _getRemediationTimeline: async function(vulnerabilityId) {
      this.cloudError = '';
      this.syncing = true;
      this.syncingMessage = 'Getting the timeline for this vulnerability...';
      let report = await Cloud.getRemediationTimeline.with({vulnerabilityId: vulnerabilityId, teamApid: this.teamApid})
      .tolerate((err)=>{
        this.cloudError = err;
        this.syncing = false;
        this.syncingMessage = '';
      });
      if(!this.cloudError) {
        this.remediationSnapshots = [];
        // Clear out existing  `remediationSnapshots` and force-render before updating
        // the result set in case Vue tries to get clever.
        await this.forceRender();
        this.remediationSnapshots = report.timelineForThisVulnerability;
        this.affectedHostsForSelectedVuln = report.affectedHosts;
        this.syncing = false;
        this.syncingMessage = '';
      }
    },

    openedRemediationModal: function() {// This draws the remediation timeline graph.
      let graphDataSetForThisVulnerability = [];
      for(let graphPoint of this.remediationSnapshots){
        let dataForThisVulnerability = {
          x: graphPoint.timestamp,
          y: graphPoint.numAffectedHosts
        };
        graphDataSetForThisVulnerability.push(dataForThisVulnerability);
      }
      new Chart('remediation-timeline',
      {
        type: 'line',
        data: {
          datasets: [{
            backgroundColor: '#BEC2DE',
            borderColor: '#8C93C0',
            borderWidth: 1,
            fill: 'origin',
            tension: 0,
            label: 'Number of hosts affected',
            data: graphDataSetForThisVulnerability,
          }]
        },
        options: {
          title: {
            display: false,
          },
          legend:{
            display: false,
          },
          tooltips: {
            mode: 'nearest',
            intersect: false
          },
          scales: {
            yAxes: [{
              ticks:{
                stepSize: 1,
                suggestedMin: 0
              }
            }],
            xAxes: [{
              type: 'time',
              time: {
                unit: 'day'
              }
            }]
          }
        }
      });

    },
    _applyNewFilters: async function(){


    },
    _computePages: function() {
      this.pages = [];
      let totalPages = Math.ceil(this.totalFilteredVulnerabilities/this.ENTRIES_PER_PAGE);
      for(let pageIndex = 0; pageIndex <= totalPages-1; pageIndex++) {
        // Suuuuper quick safeguard to keep the total # pages displayed from
        // getting unusable. It's kinda weird and makes the # of pages change
        // but at least they aren't offscreen and unclickable.
        if(totalPages > 15) {
          let isNearbyPage = Math.abs(this.currentPageIndex - pageIndex) < 3;
          let isFirstPage = pageIndex === 0;
          let isLastPage = pageIndex === totalPages-1;
          if(!isNearbyPage && (pageIndex === 1 || pageIndex === totalPages-2)) {
            this.pages.push('...');
            continue;
          } else if(!isNearbyPage && !isFirstPage && !isLastPage) {
            continue;
          }
        }
        this.pages.push(pageIndex+1);
      }
    },
  }
});
